package cn.iocoder.yudao.module.product.service.recommend;

import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO;
import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
import cn.iocoder.yudao.module.product.dal.mysql.history.ProductBrowseHistoryMapper;
import cn.iocoder.yudao.module.product.dal.mysql.spu.ProductSpuMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;

/**
 * @author 梁富贵
 * @Date 2024/10/30
 **/
@Service
public class RecommendServiceImpl implements RecommendService{

    @Resource
    private ProductBrowseHistoryMapper productBrowseHistoryMapper;

    @Resource
    private ProductSpuMapper productSpuMapper;

    @Override
    public PageResult<ProductSpuDO> getRecommend(PageParam pageParam) {
    Long userId = getLoginUserId();
    if (userId == null) {
        return new PageResult<ProductSpuDO>().setList(this.getRecommendByRandom()).setTotal(10L);
    }

    List<ProductBrowseHistoryDO> historyList = productBrowseHistoryMapper.selectListByUserId(userId);
    if (historyList == null || historyList.isEmpty()) {
        return new PageResult<ProductSpuDO>().setList(Collections.emptyList()).setTotal(0L);
    }

    List<Long> spuIds = historyList.stream().map(ProductBrowseHistoryDO::getSpuId).distinct().toList();
    if (spuIds.isEmpty()) {
        return new PageResult<ProductSpuDO>().setList(Collections.emptyList()).setTotal(0L);
    }

    List<Long> similarSpuIds = this.preliminaryCleaning(spuIds);
    List<Long> newProductIds = this.getNewProductIds(spuIds);
    newProductIds = this.combinationSimilarityAndNewProduct(similarSpuIds, newProductIds);

    int fromIndex = pageParam.getPageNo() * pageParam.getPageSize();
    int toIndex = Math.min(fromIndex + pageParam.getPageSize(), newProductIds.size());
    List<Long> newIds = newProductIds.subList(fromIndex, toIndex);

    List<ProductSpuDO> products = productSpuMapper.selectBatchIds(newIds);
    return new PageResult<ProductSpuDO>().setList(products).setTotal((long) newProductIds.size());
}


    @Override
    public List<ProductSpuDO> getRecommendByRandom() {
        return productSpuMapper.selectRecommendByRandom();
    }

    @Override
    public List<Long> preliminaryCleaning(List<Long> spuIds) {
        if(spuIds == null || spuIds.isEmpty()){
            return null;
        }

        Map<Long, Double> similarUserIds = this.getSimilarUserIds(spuIds);
        List<Long> userIds = new ArrayList<>(similarUserIds.keySet());
        List<ProductBrowseHistoryDO> historyList = productBrowseHistoryMapper.selectListByUserIds(userIds);

        return historyList.stream().map(ProductBrowseHistoryDO::getUserId).distinct().toList();
    }

    @Override
    public Map<Long, Double> getSimilarUserIds(List<Long> spuIds) {
        List<ProductBrowseHistoryDO> historyList = productBrowseHistoryMapper.getBySpuIds(spuIds);
        List<Long> userIds = historyList.stream().map(ProductBrowseHistoryDO::getUserId).distinct().toList();
        Map<Long, List<ProductBrowseHistoryDO>> spuIdMap = new HashMap<>();
        Map<Long, List<ProductBrowseHistoryDO>> spuList = new HashMap<>();
        for (Long userId : userIds) {
            List<ProductBrowseHistoryDO> list = productBrowseHistoryMapper.getByUserIdAndSpuIds(userId, spuIds);
            List<ProductBrowseHistoryDO> list2 = productBrowseHistoryMapper.selectListByUserId(userId);
            spuIdMap.put(userId, list);
            spuList.put(userId, list2);
        }
        Map<Long,List<Long>> intersection = new HashMap<>();
        Map<Long, List<Long>> union = new HashMap<>();

        for (Long userId : userIds){
            List<Long> list = Stream.concat(spuIdMap.get(userId).stream().map(ProductBrowseHistoryDO::getSpuId).toList().stream(),spuIds.stream())
                    .distinct()
                    .toList();
            intersection.put(userId,list);
            List<Long> list2 = spuList.get(userId).stream().map(ProductBrowseHistoryDO::getSpuId).toList();
            union.put(userId,list2);
        }



        Map<Long, Double> similarScore = new HashMap<>();
        for (Long userId : userIds) {
            double score = intersection.get(userId).size() / (double) union.get(userId).size();
            if(score>0.3){
                similarScore.put(userId, score);
            }
        }


        return similarScore;
    }

    @Override
    public List<Long> getNewProductIds(List<Long> spuIds) {
        List<ProductSpuDO> spus = productSpuMapper.selectBatchIds(spuIds);
        List<Long> categories = spus.stream().map(ProductSpuDO::getCategoryId).toList();

        Map<Long, Long> categoryCountMap = spus.stream()
                .map(ProductSpuDO::getCategoryId)
                .collect(Collectors.groupingBy(categoryId -> categoryId, Collectors.counting()));

        // 根据出现次数对 categoryId 进行排序（从多到少）
        List<Map.Entry<Long, Long>> sortedCategoryCounts = categoryCountMap.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .toList();
        List<Long> topCategories = sortedCategoryCounts.stream()
                .map(Map.Entry::getKey)
                .toList();

        return productSpuMapper.selectListByCategoryId(topCategories).stream().map(ProductSpuDO::getCategoryId).toList();
    }

    @Override
    public List<Long> combinationSimilarityAndNewProduct(List<Long> similarities, List<Long> newProductIds) {
        if(!similarities.isEmpty() && !newProductIds.isEmpty()){
            List<Long> newSpuIds = new ArrayList<>(Stream.concat(similarities.stream(), newProductIds.stream()).distinct().toList());
            Random random = new Random(12345);
            Collections.shuffle(newSpuIds,random);
            return newProductIds;
        }else if (similarities.isEmpty()){
            return newProductIds;
        }else {
            return similarities;
        }
    }
}
